package com.wkh.project.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wkh.project.common.ErrorCode;
import com.wkh.project.constant.CommonConstant;
import com.wkh.project.exception.BusinessException;
import com.wkh.project.exception.ThrowUtils;
import com.wkh.project.mapper.QuestionBankQuestionMapper;
import com.wkh.project.model.dto.questionBankQuestion.QuestionBankQuestionQueryRequest;
import com.wkh.project.model.entity.Question;
import com.wkh.project.model.entity.QuestionBank;
import com.wkh.project.model.entity.QuestionBankQuestion;
import com.wkh.project.model.entity.User;
import com.wkh.project.model.vo.QuestionBankQuestionVO;
import com.wkh.project.model.vo.UserVO;
import com.wkh.project.service.QuestionBankQuestionService;
import com.wkh.project.service.QuestionBankService;
import com.wkh.project.service.QuestionService;
import com.wkh.project.service.UserService;
import com.wkh.project.utils.SqlUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.aop.framework.AopContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * 题目与题库关系表服务实现
 *
 * @author wangkehua
 */
@Service
@Slf4j
public class QuestionBankQuestionServiceImpl extends ServiceImpl<QuestionBankQuestionMapper, QuestionBankQuestion> implements QuestionBankQuestionService {

    @Resource
    private ExecutorService customExecutor;

    @Resource
    private UserService userService;
    @Resource
    private QuestionBankService questionBankService;

    @Resource
    @Lazy //避免循环引用
    private QuestionService questionService;

    /**
     * 校验数据
     *
     * @param questionBankQuestion
     * @param add      对创建的数据进行校验
     */
    @Override
    public void validQuestionBankQuestion(QuestionBankQuestion questionBankQuestion, boolean add) {
        ThrowUtils.throwIf(questionBankQuestion == null, ErrorCode.PARAMS_ERROR);
        //判断题目和题库是否存在
        Long questionId = questionBankQuestion.getQuestionId();
        Long questionBankId = questionBankQuestion.getQuestionBankId();
        if (questionId == null || questionBankId == null) {
            ThrowUtils.throwIf(true, ErrorCode.PARAMS_ERROR, "题目或题库不存在");
        }
        Question question = questionService.getById(questionId);
        QuestionBank questionBank = questionBankService.getById(questionBankId);
        if (question == null || questionBank == null) {
            ThrowUtils.throwIf(true, ErrorCode.PARAMS_ERROR, "题目或题库不存在");
        }

        // todo 从对象中取值
        /*String title = questionBankQuestion.getTitle();
        // 创建数据时，参数不能为空
        if (add) {
            // todo 补充校验规则
            ThrowUtils.throwIf(StringUtils.isBlank(title), ErrorCode.PARAMS_ERROR);
        }
        // 修改数据时，有参数则校验
        // todo 补充校验规则
        if (StringUtils.isNotBlank(title)) {
            ThrowUtils.throwIf(title.length() > 80, ErrorCode.PARAMS_ERROR, "标题过长");
        }*/
    }

    /**
     * 获取查询条件
     *
     * @param questionBankQuestionQueryRequest
     * @return
     */
    @Override
    public QueryWrapper<QuestionBankQuestion> getQueryWrapper(QuestionBankQuestionQueryRequest questionBankQuestionQueryRequest) {
        QueryWrapper<QuestionBankQuestion> queryWrapper = new QueryWrapper<>();
        if (questionBankQuestionQueryRequest == null) {
            return queryWrapper;
        }
        // todo 从对象中取值
        Long id = questionBankQuestionQueryRequest.getId();
        Long notId = questionBankQuestionQueryRequest.getNotId();
        String searchText = questionBankQuestionQueryRequest.getSearchText();
        Long questionId = questionBankQuestionQueryRequest.getQuestionId();
        Long questionBankId = questionBankQuestionQueryRequest.getQuestionBankId();
        String sortField = questionBankQuestionQueryRequest.getSortField();
        String sortOrder = questionBankQuestionQueryRequest.getSortOrder();
        Long userId = questionBankQuestionQueryRequest.getUserId();
        // todo 补充需要的查询条件
        // 从多字段中搜索
        if (StringUtils.isNotBlank(searchText)) {
            // 需要拼接查询条件
            queryWrapper.and(qw -> qw.like("title", searchText).or().like("content", searchText));
        }
        // 模糊查询
        // 精确查询
        queryWrapper.ne(ObjectUtils.isNotEmpty(notId), "id", notId);
        queryWrapper.eq(ObjectUtils.isNotEmpty(id), "id", id);
        queryWrapper.eq(ObjectUtils.isNotEmpty(userId), "userId", userId);
        queryWrapper.eq(ObjectUtils.isNotEmpty(questionId), "questionId", questionId);
        queryWrapper.eq(ObjectUtils.isNotEmpty(questionBankId), "questionBankId", questionBankId);
        // 排序规则
        queryWrapper.orderBy(SqlUtils.validSortField(sortField),
                sortOrder.equals(CommonConstant.SORT_ORDER_ASC),
                sortField);
        return queryWrapper;
    }

    /**
     * 获取题目与题库关系表封装
     *
     * @param questionBankQuestion
     * @param request
     * @return
     */
    @Override
    public QuestionBankQuestionVO getQuestionBankQuestionVO(QuestionBankQuestion questionBankQuestion, HttpServletRequest request) {
        // 对象转封装类
        QuestionBankQuestionVO questionBankQuestionVO = QuestionBankQuestionVO.objToVo(questionBankQuestion);

        // todo 可以根据需要为封装对象补充值，不需要的内容可以删除
        // region 可选
        // 1. 关联查询用户信息
        Long userId = questionBankQuestion.getUserId();
        User user = null;
        if (userId != null && userId > 0) {
            user = userService.getById(userId);
        }
        UserVO userVO = userService.getUserVO(user);
        questionBankQuestionVO.setUser(userVO);
        // endregion

        return questionBankQuestionVO;
    }

    /**
     * 分页获取题目与题库关系表封装
     *
     * @param questionBankQuestionPage
     * @param request
     * @return
     */
    @Override
    public Page<QuestionBankQuestionVO> getQuestionBankQuestionVOPage(Page<QuestionBankQuestion> questionBankQuestionPage, HttpServletRequest request) {
        List<QuestionBankQuestion> questionBankQuestionList = questionBankQuestionPage.getRecords();
        Page<QuestionBankQuestionVO> questionBankQuestionVOPage = new Page<>(questionBankQuestionPage.getCurrent(), questionBankQuestionPage.getSize(), questionBankQuestionPage.getTotal());
        if (CollUtil.isEmpty(questionBankQuestionList)) {
            return questionBankQuestionVOPage;
        }
        // 对象列表 => 封装对象列表
        List<QuestionBankQuestionVO> questionBankQuestionVOList = questionBankQuestionList.stream().map(questionBankQuestion -> {
            return QuestionBankQuestionVO.objToVo(questionBankQuestion);
        }).collect(Collectors.toList());

        // todo 可以根据需要为封装对象补充值，不需要的内容可以删除
        // region 可选
        // 1. 关联查询用户信息
        Set<Long> userIdSet = questionBankQuestionList.stream().map(QuestionBankQuestion::getUserId).collect(Collectors.toSet());
        Map<Long, List<User>> userIdUserListMap = userService.listByIds(userIdSet).stream()
                .collect(Collectors.groupingBy(User::getId));
        // 2. 已登录，获取用户点赞、收藏状态
        User loginUser = userService.getLoginUserPermitNull(request);
        if (loginUser != null) {
            loginUser = userService.getLoginUser(request);
        }
        // 填充信息
        questionBankQuestionVOList.forEach(questionBankQuestionVO -> {
            Long userId = questionBankQuestionVO.getUserId();
            User user = null;
            if (userIdUserListMap.containsKey(userId)) {
                user = userIdUserListMap.get(userId).get(0);
            }
            questionBankQuestionVO.setUser(userService.getUserVO(user));
        });
        // endregion

        questionBankQuestionVOPage.setRecords(questionBankQuestionVOList);
        return questionBankQuestionVOPage;
    }

    /**
     * 批量添加题目到题库（事务，仅供内部调用）
     * @param questionIds 题目id列表
     * @param bankId 题库id
     * @param loginUser 执行批量添加操作的用户
     */
    @Override
    public void batchAddQuestionsToBank(List<Long> questionIds, Long bankId, User loginUser) {
        //0.定义用于保存所有批次的任务的列表
        ArrayList<CompletableFuture<Void>> futures = new ArrayList<>();
        //1.参数校验
        ThrowUtils.throwIf(questionIds == null || questionIds.isEmpty(), ErrorCode.PARAMS_ERROR,"题目id为空");
        ThrowUtils.throwIf(bankId == null || bankId<=0, ErrorCode.PARAMS_ERROR,"题库id为空");
        ThrowUtils.throwIf(loginUser == null, ErrorCode.PARAMS_ERROR,"用户为空");
        //2.根据questionIds查询题目
        //进行优化，不用select * ，只查询questionId字段
        List<Long> vaildQuestionIdList = questionService.listObjs(Wrappers.lambdaQuery(Question.class)
                                                     .select(Question::getId)
                                                     .in(Question::getId, questionIds),obj->(Long)obj);
        ThrowUtils.throwIf(vaildQuestionIdList.isEmpty(), ErrorCode.PARAMS_ERROR,"合法正确的questionIds为空");
        //3.1 从数据库中取出已经存在于题目题库关系表中的数据
        //进行优化，不用select * ，只查询questionId字段
        List<Long> existQuestionBankQuestionIds = this.listObjs(Wrappers.lambdaQuery(QuestionBankQuestion.class)
                .select(QuestionBankQuestion::getQuestionId)
                .eq(QuestionBankQuestion::getQuestionBankId, bankId)
                .in(QuestionBankQuestion::getQuestionId, vaildQuestionIdList),obj->(Long)obj);
        if (existQuestionBankQuestionIds != null && !existQuestionBankQuestionIds.isEmpty()) {
            //3.2 过滤掉已经存在于数据库中的数据
            vaildQuestionIdList = vaildQuestionIdList.stream()
                    .filter(questionId -> !existQuestionBankQuestionIds.contains(questionId))
                    .toList();
            ThrowUtils.throwIf(vaildQuestionIdList.isEmpty(), ErrorCode.PARAMS_ERROR,"所有数据都存在于题库中");
        }
        //4.根据bankId查询题库
        QuestionBank questionBank = questionBankService.getById(bankId);
        ThrowUtils.throwIf(questionBank == null, ErrorCode.PARAMS_ERROR,"题库不存在");
        //5.分批插入
        int batchSize = 1000;
        int total = vaildQuestionIdList.size();
        for(int i = 0;i<total; i+=batchSize){
            //5.1 计算每批数据的起始和结束索引
            int end = Math.min(i+batchSize, total);
            List<Long> subList = vaildQuestionIdList.subList(i, end);
            //5.2 将每批数据转换为QuestionBankQuestion对象
            List<QuestionBankQuestion> questionBankQuestions = subList.stream().map(questionId -> {
                QuestionBankQuestion questionBankQuestion = new QuestionBankQuestion();
                questionBankQuestion.setQuestionBankId(bankId);
                questionBankQuestion.setQuestionId(questionId);
                questionBankQuestion.setUserId(loginUser.getId());
                return questionBankQuestion;
            }).toList();
            //5.3 使用事务处理每批数据,使用AOP代理对象调用内部方法
            QuestionBankQuestionService questionBankQuestionService = (QuestionBankQuestionServiceImpl) AopContext.currentProxy();
            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                questionBankQuestionService.batchAddQuestionsToBankInner(questionBankQuestions);
            },customExecutor).exceptionally(e->{
                log.error("任务执行失败");
                throw new BusinessException(ErrorCode.OPERATION_ERROR,"批量添加题目到题库失败");
            });
            futures.add(future);
        }
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
    }

    @Transactional(rollbackFor = Exception.class)
    @Async
    @Override
    public void batchAddQuestionsToBankInner(List<QuestionBankQuestion> questionBankQuestionList) {
        try {
            boolean result = this.saveBatch(questionBankQuestionList);
            ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "批量添加题目到题库失败");
        } catch (DataIntegrityViolationException e) {
            log.error("数据库唯一键冲突或违反其他完整性约束");
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "题目已存在于该题库，无法重复添加");
        } catch (DataAccessException e) {
            log.error("数据库连接问题、事务问题等导致操作失败");
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "数据库操作失败");
        } catch (Exception e) {
            // 捕获其他异常，做通用处理
            log.error("添加题目到题库时发生未知错误");
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "向题库添加题目失败");
        }
    }

    @Override
    public void batchRemoveQuestionsFromBank(List<Long> questionIdList, Long questionBankId) {
        // 参数校验
        ThrowUtils.throwIf(CollUtil.isEmpty(questionIdList), ErrorCode.PARAMS_ERROR, "题目列表为空");
        ThrowUtils.throwIf(questionBankId == null || questionBankId <= 0, ErrorCode.PARAMS_ERROR, "题库非法");
        //查询题目题库关系表中存在的数据
        List<Long> vaildQuestionIdList = this.listObjs(Wrappers.lambdaQuery(QuestionBankQuestion.class)
                .select(QuestionBankQuestion::getQuestionId)
                .eq(QuestionBankQuestion::getQuestionBankId, questionBankId)
                .in(QuestionBankQuestion::getQuestionId, questionIdList),obj -> (Long)obj);
        ThrowUtils.throwIf(CollUtil.isEmpty(vaildQuestionIdList), ErrorCode.PARAMS_ERROR, "题目题库关系数据不存在");
        //拿到数据的questionId
        // 分批次删除
        int batchSize = 1000;
        int total = vaildQuestionIdList.size();
        for (int i = 0; i < total; i += batchSize) {
            int end = i + batchSize;
            List<Long> subList = vaildQuestionIdList.subList(i, Math.min(total, end));
            // 使用AOP代理对象调用内部方法
            QuestionBankQuestionService questionBankQuestionService = (QuestionBankQuestionServiceImpl) AopContext.currentProxy();
            questionBankQuestionService.batchRemoveQuestionsFromBankInner(subList, questionBankId);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void batchRemoveQuestionsFromBankInner(List<Long> questionIdList, Long questionBankId) {
        try {
            LambdaQueryWrapper<QuestionBankQuestion> lambdaQueryWrapper = Wrappers.lambdaQuery(QuestionBankQuestion.class)
                    .eq(QuestionBankQuestion::getQuestionBankId, questionBankId)
                    .in(QuestionBankQuestion::getQuestionId, questionIdList);
            boolean result = this.remove(lambdaQueryWrapper);
            ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "从题库移除题目失败");
        } catch (BusinessException e) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "从题库移除题目失败");
        }
    }

}
